#include "swagme.h"
#include "config.h"
#include "Game.h"
#include "Display.h"
#include "Player.h"
#include "Scene.h"
#include "Swag.h"
#include "sound.h"
#include "Nerd.h"
#include "Babe.h"

#include <iostream>
#include <algorithm>
using std::cout;
using std::cerr;
using std::endl;

// 2 minutes for each screen (for now)
#define TIME_LEFT (2*60)

// TODO: put this somewhere into the XML file (maybe a <chrome> element?)
const char* szStatusAreaBackgroundImage = "resources/graphics/status-area.png";

Game::Game() : 
    mTimeLeftInSec(TIME_LEFT), 
    mStartTimeInMs(SDL_GetTicks()), 
    mpDisplay(new Display()),
    mpPlayer(NULL),
    mpConfig(InitConfig("resources/wad.xml")),
    mpCurLevel(NULL),
    mbResortRenderables(true),
    sound(NULL),
    mbUpKeyPressed(false),
    mbRightKeyPressed(false),
    mbDownKeyPressed(false),
    mbLeftKeyPressed(false),
    theWall(NULL),
    mpScene(NULL),
    mpStatusAreaSurf(NULL),
    mbStatusDirty(true),
    mLastPhotoTimeInMs(mStartTimeInMs)
{
  if(!mpDisplay) {
    cerr << "Error!  Could not initialize Display" << endl;
    exit(4);
  }
  cout << "Game and Display initialized." << endl;
  Init();
}

Game::~Game()
{
  // release the audio device
  if (sound) delete sound;
  
  // wipe this collection clean
  mRenderables.clear();

  if (mColMap.map) { delete[] mColMap.map; }

  for (PhotoIter iter=mPhotos.begin();  iter != mPhotos.end();  iter++) {
    delete *iter;
  }
  mPhotos.clear();
  
  for (unsigned int idx = 0; idx < mSwaggage.size(); idx++) {
    delete mSwaggage[idx];
  }
  mSwaggage.clear();

  for (unsigned int idx = 0; idx < mBabes.size(); idx++) {
    delete mBabes[idx];
  }
  mBabes.clear();

  for (unsigned int idx = 0; idx < mNerds.size(); idx++) {
    delete mNerds[idx];
  }
  mNerds.clear();

  for( map<string,Sprite*>::iterator spriteIt = mSprites.begin(); spriteIt != mSprites.end(); ++spriteIt) {
    Sprite* s = spriteIt->second;
    delete s;
  }
  mSprites.clear();

  // destroy chrome
  if(mpStatusAreaSurf) { 
    SDL_FreeSurface(mpStatusAreaSurf); 
    mpStatusAreaSurf = NULL;
    Photo::ClearBorder(); // free the border image surfaces
  }

  if(mpPlayer) {
    delete mpPlayer;
    mpPlayer = NULL;
  }

  if(mpConfig) {
    delete mpConfig;
    mpConfig = NULL;
  }

  if(mpScene) {
    delete mpScene;
    mpScene = NULL;
  }

  if(mpDisplay) {
    delete mpDisplay;
    mpDisplay = NULL;
  }

  if(theWall) {
    delete theWall;
    theWall = NULL;
  }

  cout << "Game and Display destroyed." << endl;
}

int Game::getSwagPoints(sm_swag_type type) {
    if(sm_swag_pen == type) { return 8; }
    else if(sm_swag_hat == type) { return 12; }
    else if(sm_swag_yoyo == type) { return 16; }
    else if(sm_swag_tshirt == type) { return 20; }
    return 10;
}

// read in all resources
// find the first conference
// set up player position
void Game::Init()
{
  InitChrome();
  InitSprites();
  InitSounds();
  
  // get all levels (order matters in the XML doc)
  int numLevels = mpConfig->getElementsByTagName("conference", &mAllLevels);
  if(!numLevels) {
    cerr << "Error!  Could not find any <conference> elements in wad.xml" << endl;
    exit(2000);
  }

  // find the first conference
  mpCurLevel = mAllLevels[0];
  if(!mpCurLevel) {
    cerr << "Error!  No suitable levels found (check order attributes on <conference> elements)" << endl;
    exit(-1);
  }
  int lev_width = ATOI(mpCurLevel->attr["width"]);
  int lev_height = ATOI(mpCurLevel->attr["height"]);
  cout << "First level was '" << mpCurLevel->attr["name"] << "'" << " (" << lev_width << "," << lev_height << ")" << endl;

  // set up background map
  mpScene = new Scene(mpCurLevel->attr["background"].c_str(), mpCurLevel->attr["collisionMap"].c_str(), mpCurLevel->attr["floor"].c_str());
  if(!mpScene) {
    cerr << "Error!  Could not initialize scene" << endl;
    exit(5);
  }

  // InitScene() initializes the colmap
  InitScene();
  
  // read in swag from <conference> element
  NodeArray swagNodes;
  mpCurLevel->getElementsByTagName("swag", &swagNodes);
  for(NodeIter swagIt = swagNodes.begin(); swagIt != swagNodes.end(); ++swagIt) {
    XMLNode* node = *swagIt;
    cout << "Found <swag> with type=" << node->attr["type"] << endl;

    sm_swag_type swagType;
    if(node->attr["type"] == "PEN") { swagType = sm_swag_pen; }
    else if(node->attr["type"] == "HAT") { swagType = sm_swag_hat; }
    else if(node->attr["type"] == "YOYO") { swagType = sm_swag_yoyo; }
    else if(node->attr["type"] == "TSHIRT") { swagType = sm_swag_tshirt; }
    else {
      cerr << "Warning!  Ignoring <swag> element with unknown swag type '" << node->attr["type"] << "'" << endl;
      continue;
    }

    if(mSprites.find(node->attr["sprite"]) == mSprites.end()) {
      cerr << "Warning!  Ignoring <swag> element with unknown sprite '" << mSprites[node->attr["sprite"]] << "'" << endl;
      continue;
    }
    
    // add this id to our ID map
    vector<string>& vec = mSpriteIds[swagType];
    if( find(vec.begin(), vec.end(), node->attr["sprite"]) == vec.end() ) {
      vec.push_back(node->attr["sprite"]);
      cout << "Added '" << node->attr["sprite"] << "' to mSpriteIds" << endl;
    }

    int x = 0;
    int y = 0;
    bool bFoundLocation = false;
    while(!bFoundLocation) {
      x = ATOI(node->attr["x"]);
      y = ATOI(node->attr["y"]);
      if(node->attr["x"] == "RANDOM") {
        x = random(30, lev_width-30);
      }
      if(node->attr["y"] == "RANDOM") {
        y = random(30, lev_width-30);
      }

      if( node->attr["x"] == "RANDOM" || node->attr["y"] == "RANDOM") {
        // TODO:  Swag is hard-coded to 12x12, we need to change this 
        // code here if it ever is not so.  This means that we have to
        // check 4 colmap indices (swag takes up a 2x2 square in colmap)
        int colx = x/mColMap.cellInPixels;
        int coly = y/mColMap.cellInPixels;
        int index = coly*mColMap.width + colx;
        if(!mColMap.map[index] && !mColMap.map[index+1] && 
          !mColMap.map[index+mColMap.width] && !mColMap.map[index+mColMap.width+1]) 
        {
          bFoundLocation = true;
        }
      }
      else {
        bFoundLocation = true;
      }

    } // while(!bFoundLocation)

    Swag* aSwag = new Swag(mSprites[node->attr["sprite"]], x, y, swagType, getSwagPoints(swagType));
    mSwaggage.push_back( aSwag ); // owned
    mRenderables.push_back( aSwag ); // not owned
    mColMap.addTouchableObject( aSwag ); // not owned

  }

  // read in babes from <conference> element
  NodeArray babeNodes;
  mpCurLevel->getElementsByTagName("babe", &babeNodes);
  for(NodeIter babeIt = babeNodes.begin(); babeIt != babeNodes.end(); ++babeIt) {
    XMLNode* node = *babeIt;
    cout << "Found <babe>" << endl;

    if(mSprites.find(node->attr["sprite"]) == mSprites.end()) {
      cerr << "Error!  Could not find sprite '" << mSprites[node->attr["sprite"]] << "'" << endl;
      exit(-1);
    }

    int x = 0;
    int y = 0;
    int speed = 0;
    int generosity = 0;
    bool bFoundLocation = false;
    while(!bFoundLocation) {
      x = ATOI(node->attr["x"]);
      y = ATOI(node->attr["y"]);
      speed = ATOI(node->attr["speed"]);
      generosity = ATOI(node->attr["generosity"]);

      if(node->attr["x"] == "RANDOM") {
        x = random(30, lev_width-30);
      }
      if(node->attr["y"] == "RANDOM") {
        y = random(30, lev_width-30);
      }
      if(speed == 0) {
        speed = CHARACTER_DEFAULT_SPEED;
      }
      if(generosity == 0) {
        generosity = BABE_DEFAULT_GENEROSITY;
      }

      if(node->attr["y"] == "RANDOM" || node->attr["y"] == "RANDOM") {
        // TODO:  Babes are hard-coded to 12x12, we need to change this 
        // code here if it ever is not so.  This means that we have to
        // check 4 colmap indices (npc takes up a 2x2 square in colmap)
        int colx = x/mColMap.cellInPixels;
        int coly = y/mColMap.cellInPixels;
        int index = coly*mColMap.width + colx;
        if(!mColMap.map[index] && !mColMap.map[index+1] && 
          !mColMap.map[index+mColMap.width] && !mColMap.map[index+mColMap.width+1]) 
        {
          bFoundLocation = true;
        }
      }
      else {
        bFoundLocation = true;
      }
    } // while(!bFoundLocation)

    sm_direction babeFacing;
    if(node->attr["facing"] == "RANDOM") {
      babeFacing = (sm_direction)(random(MIN_DIR, MAX_DIR));
    }
    else {
      int tempFacing = ATOI(node->attr["facing"]);
      if(tempFacing < MIN_DIR || tempFacing > MAX_DIR) {
        cerr << "Warning!  XML file had invalid value for NPC facing: " << babeFacing << endl;
        tempFacing = sm_dir_down;
      }
      babeFacing = (sm_direction)(tempFacing);
    }

    Babe* aBabe = new Babe(mSprites[node->attr["sprite"]], x, y, speed, generosity);
    aBabe->SetFacing( babeFacing );
    mBabes.push_back( aBabe ); // owned
    mRenderables.push_back( aBabe ); // not owned
    mColMap.addTouchableObject( aBabe ); // not owned
  }

  // read in nerds from <conference> element
  NodeArray nerdNodes;
  mpCurLevel->getElementsByTagName("nerd", &nerdNodes);
  for(NodeIter nerdIt = nerdNodes.begin(); nerdIt != nerdNodes.end(); ++nerdIt) {
    XMLNode* node = *nerdIt;
    cout << "Found <nerd>" << endl;

    if(mSprites.find(node->attr["sprite"]) == mSprites.end()) {
      cerr << "Error!  Could not find sprite '" << mSprites[node->attr["sprite"]] << "'" << endl;
      exit(-1);
    }

    int x = 0;
    int y = 0;
    int speed = 0;
    bool bFoundLocation = false;
    while(!bFoundLocation) {
      x = ATOI(node->attr["x"]);
      y = ATOI(node->attr["y"]);
      speed = ATOI(node->attr["speed"]);

      if(node->attr["x"] == "RANDOM") {
        x = random(30, lev_width-30);
      }
      if(node->attr["y"] == "RANDOM") {
        y = random(30, lev_width-30);
      }
      if(speed == 0) {
        speed = CHARACTER_DEFAULT_SPEED;
      }

      if(node->attr["y"] == "RANDOM" || node->attr["y"] == "RANDOM") {
        // TODO:  Nerds are hard-coded to 12x12, we need to change this 
        // code here if it ever is not so.  This means that we have to
        // check 4 colmap indices (npc takes up a 2x2 square in colmap)
        int colx = x/mColMap.cellInPixels;
        int coly = y/mColMap.cellInPixels;
        int index = coly*mColMap.width + colx;
        if(!mColMap.map[index] && !mColMap.map[index+1] && 
          !mColMap.map[index+mColMap.width] && !mColMap.map[index+mColMap.width+1]) 
        {
          bFoundLocation = true;
        }
      }
      else {
        bFoundLocation = true;
      }
    } // while(!bFoundLocation)

    sm_direction nerdFacing;
    if(node->attr["facing"] == "RANDOM") {
      nerdFacing = (sm_direction)(random(MIN_DIR, MAX_DIR));
    }
    else {
      int tempFacing = ATOI(node->attr["facing"]);
      if(tempFacing < MIN_DIR || tempFacing > MAX_DIR) {
        cerr << "Warning!  XML file had invalid value for Nerd facing: " << nerdFacing << endl;
        tempFacing = sm_dir_down;
      }
      nerdFacing = (sm_direction)(tempFacing);
    }

    Nerd* aNerd = new Nerd(mSprites[node->attr["sprite"]], x, y, speed);
    aNerd->SetFacing( nerdFacing );
    mNerds.push_back( aNerd ); // owned
    mRenderables.push_back( aNerd ); // not owned
    mColMap.addTouchableObject( aNerd ); // not owned
  } // nerds

  // initialize player
  // - get initial player position
  XMLNode* ps = mpCurLevel->getElementByTagName("playerStart");
  if(!ps ) {
    cerr << "Error!  Could not find <playerStart> element in first <conference> element" << endl;
    exit(-1);
  }
  // - get player sprite
  if(mSprites.find("player-sprite") == mSprites.end())  { 
    cerr << "Error!  Could not find player-sprite <sprite> in XML" << endl; 
    exit(-1); 
  }
  // - create player
  int speed = ATOI(ps->attr["speed"]);
  if(speed == 0) { speed = CHARACTER_DEFAULT_SPEED; }
  mpPlayer = new Player(mSprites["player-sprite"],
                  ATOI(ps->attr["x"]),
                  ATOI(ps->attr["y"]),
                  speed);
  int playerFacing = ATOI(ps->attr["facing"]);
  if(playerFacing < MIN_DIR || playerFacing > MAX_DIR) { 
    cout << "WARNING:  XML file had an invalid value for playerStart facing: " << playerFacing << endl;
    playerFacing = sm_dir_down;
  }
  mpPlayer->SetFacing( (sm_direction)playerFacing );
  mRenderables.push_back(mpPlayer); // not owned
  mColMap.addTouchableObject( mpPlayer ); // not owned

  dumpColMap();
}

void Game::InitSprites()
{
  cout << "in InitSprites" << endl;
  // get the resources element
  XMLNode* pResources = mpConfig->getElementByTagName( "resources" );
  if(!pResources) { return; }

  // set up sprite resources
  vector<XMLNode*> spriteNodes;
  int numSprites = pResources->getElementsByTagName("sprite", &spriteNodes);
  if(!numSprites) {
    cerr << "Error!  Could not find any <sprite> elements in wad.xml" << endl;
    exit(2004);
  }
  for(NodeIter spriteIt = spriteNodes.begin(); spriteIt != spriteNodes.end(); ++spriteIt) {
    XMLNode* node = *spriteIt;
    Sprite* sprite;

    if(node->id().length() == 0) { cerr << "Error!  Sprite id is blank" << endl; exit(2005); }
    if(node->attr["src"].length() == 0) { cerr << "Error!  Sprite '" << node->id() << "' does not have a src attribute" << endl; exit(2006); }
    if(node->attr["shadow"].length() == 0) {
      sprite = new Sprite(node->attr["src"].c_str());
    } else {
      sprite = new Sprite(node->attr["src"].c_str(), node->attr["shadow"].c_str());
    }
    mSprites[node->id()] = sprite;
  }
}

void Game::InitSounds()
{
  cout << "in InitSounds" << endl;
  // get the resources element
  XMLNode* pResources = mpConfig->getElementByTagName( "resources" );
  if(!pResources) { return; }

  sound = new Sound();
  vector<XMLNode*> soundNodes;
  int numSounds = pResources->getElementsByTagName("sound", &soundNodes);
  for(NodeIter soundIt = soundNodes.begin(); soundIt != soundNodes.end(); ++soundIt) {
    XMLNode* node = *soundIt;
    // Sound* sound;

    if(node->id().length() == 0) { cerr << "Error!  Sound id is blank" << endl; exit(2007); }
    if(node->attr["src"].length() == 0) { cerr << "Error!  Sound '" << node->id() << "' does not have a src attribute" << endl; exit(2008); }
    sound->Load(node->attr["src"],node->attr["id"]);
    // sound->Play(node->attr["id"]);
  }
  cout << "Loaded Sounds" << endl;
}

void Game::InitScene()
{
  theWall = new DummyWall;
  mpScene->InitScene();

  // CollisionMap construction
  mColMap.theWall = theWall;
  mColMap.cellInPixels = 8; // each square represents an 8x8 pixel region
  mColMap.width = ATOI(mpCurLevel->attr["width"])/mColMap.cellInPixels;
  mColMap.height = ATOI(mpCurLevel->attr["height"])/mColMap.cellInPixels;

  crunchCollisionMap(mpScene->CollisionMap);
}

/*
 * Return the pixel value at (x, y)
 * NOTE: The surface must be locked before calling this!
 * (from the SDL docs at file:///usr/share/doc/packages/SDL-devel/html/guidevideo.html#AEN90)
 */
Uint32 getpixel(SDL_Surface *surface, int x, int y)
{
  int bpp = surface->format->BytesPerPixel;
  /* Here p is the address to the pixel we want to retrieve */
  Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;

  switch(bpp) {
    case 1:
      return *p;

    case 2:
      return *(Uint16 *)p;

    case 3:
      if(SDL_BYTEORDER == SDL_BIG_ENDIAN)
        return p[0] << 16 | p[1] << 8 | p[2];
      else
        return p[0] | p[1] << 8 | p[2] << 16;

    case 4:
      return *(Uint32 *)p;

    default:
      return 0;       /* shouldn't happen, but avoids warnings */
  }
}

void Game::crunchCollisionMap(SDL_Surface *s) {
  int size = mColMap.width * mColMap.height;
  mColMap.map = new TouchableObject*[size];
//  cout << "Crunching: " << hex;
  for (int ix=0, iy=0; (ix<s->w) && (iy<s->h); ix++,iy++) {
//    cout << getpixel(s,ix, iy) << " ";
  }

  // ensure that s->w and s->h correspond to the same size as mColMap.map
  if ((mColMap.width * mColMap.cellInPixels != s->w) || (mColMap.height * mColMap.cellInPixels != s->h)) {
    cout << "Error: mColMap dimensions don't match image dimensions mColMap.width:" << mColMap.width << " s->w: " << s->w << " mColMap.height: " << mColMap.height << " s->h: " << s->h << endl;
    exit(-1);
  }

  // We read the collisionMap one cell at a time. So we start at (0,0) then read all pixels up to (8,8). This determines one entry in mColMap.map. After that we move on to the cell at (8,0) to (16,8)
  for (unsigned int cx=0; cx < mColMap.width; cx++) {
    for (unsigned int cy=0; cy < mColMap.height; cy++) {
      int pixelCount=0;
      // read in one cell from the full scale map
      for (unsigned int ix=0; ix < mColMap.cellInPixels; ix++) {
        for (unsigned int iy=0; iy < mColMap.cellInPixels; iy++) {
          // TODO: this is just the colour that the Gimp chose for transparent in this image but I think there's an alpha channel that could be used instead
          // get the current pixel
          if (getpixel(s, (cx * mColMap.cellInPixels) + ix,  (cy * mColMap.cellInPixels) + iy)== 0) {
          } else {
            pixelCount++;
          }
        }
      }
      // compute the map value from the full scale map
      if (pixelCount < 32) {
        mColMap.map[cx + cy * mColMap.width]=NULL;
      } else {
        mColMap.map[cx + cy * mColMap.width]=theWall;
      }
    }
  }
  cout << dec << endl;
}

void Game::dumpColMap() {
  cout << "Collision Map: " << endl;
  for (unsigned int iy=0; iy < mColMap.height; iy++) {
    for (unsigned int ix=0; ix < mColMap.width; ix++) {
      TouchableObject* obj = mColMap.map[ix + iy * mColMap.width];
      if (obj) {
        sm_cd_object type = obj->getObjectType();
        switch(type) {
          case sm_cdo_wall: cout << "X"; break;
          case sm_cdo_player: cout << "P"; break;
          case sm_cdo_nerd: cout << "N"; break;
          case sm_cdo_babe: cout << "B"; break;
          case sm_cdo_swag: cout << "S"; break;
        } // switch(type)
      } else {
        cout << " ";
      }
    }
    cout << endl;
  }
}

// primarily what this is does is update the time left
// and display to the status area
void Game::Update(Uint8* keystate)
{
  if(IsGameOver()) { return; }

  if(mTimeLeftInSec > 0) {
    int oldTime = mTimeLeftInSec;
    mTimeLeftInSec = TIME_LEFT - (SDL_GetTicks() - mStartTimeInMs)/1000;
    if(mTimeLeftInSec != oldTime) {
//    cout << "Time Left (s) = " << mTimeLeftInSec << " seconds." << endl;
    }
  }

  // keyboard controls:
  // space bar - take a picture
  if (keystate[SDLK_SPACE]) {
    // minimum time between photos is PHOTO_DELAY_MS ms
    // currently the spacebar can be held down for repeats
    if (mLastPhotoTimeInMs + PHOTO_DELAY_MS < SDL_GetTicks()) {
      SDL_Rect playerPos = mpPlayer->getBox();
      Photo *photo = new Photo();
      // TODO: consts, not 16
      int status = photo->Shoot(mpDisplay->GetScreen(), playerPos.x - 16, playerPos.y - 16);
      mPhotos.push_back(photo); // delete photos when mPhotos is disposed
      cout << "Taking photo (status " << status << ")" << endl;
      // reset photo timer
      mLastPhotoTimeInMs = SDL_GetTicks();
    }
  }

  // directions:
  //  w,a,d,s or up, left, down, right arrows or num pad equiv
  //  static sm_direction mpPlayer->Facing = sm_dir_none;
  // if we want the player to just keep moving until they change direction, then this can be set to true or removed
  // I kind of like that - it's more like pacman
  bool moveKeyPressed=false; 
  
  // save the previous position of the player
  mpPlayer->update();

  if (keystate[SDLK_w] || keystate[SDLK_UP] || keystate[SDLK_KP8]) {
    // every time the up key is pressed, add a keypress to the stack
    if(!mbUpKeyPressed) { 
      // add an "up" key press to the stack
      mKeyPressStack.push_front(sm_dir_up);
      mbUpKeyPressed = true;
    }
  } 
  else { 
    // every time the up key is released, remove the keypress from the stack
    if(mbUpKeyPressed) {
      // remove any "up" key presses from the stack
      for(KeyIter it = mKeyPressStack.begin(); it != mKeyPressStack.end(); ++it) {
        if(*it == sm_dir_up) { mKeyPressStack.erase(it); break;}
      }
      mbUpKeyPressed = false;
    }
  }

  if (keystate[SDLK_a] || keystate[SDLK_LEFT] || keystate[SDLK_KP4]) {
    if(!mbLeftKeyPressed) { 
      mKeyPressStack.push_front(sm_dir_left);
      mbLeftKeyPressed = true;
    }
  } 
  else { 
    if(mbLeftKeyPressed) {
      for(KeyIter it = mKeyPressStack.begin(); it != mKeyPressStack.end(); ++it) {
        if(*it == sm_dir_left) { mKeyPressStack.erase(it); break;}
      }
      mbLeftKeyPressed = false;
    }
  }

  if (keystate[SDLK_s] || keystate[SDLK_DOWN] || keystate[SDLK_KP2]) {
    if(!mbDownKeyPressed) { 
      mKeyPressStack.push_front(sm_dir_down);
      mbDownKeyPressed = true;
    }
  } 
  else { 
    if(mbDownKeyPressed) {
      for(KeyIter it = mKeyPressStack.begin(); it != mKeyPressStack.end(); ++it) {
        if(*it == sm_dir_down) { mKeyPressStack.erase(it); break;}
      }
      mbDownKeyPressed = false;
    }
  }

  if (keystate[SDLK_d] || keystate[SDLK_RIGHT] || keystate[SDLK_KP6]) {
    if(!mbRightKeyPressed) { 
      mKeyPressStack.push_front(sm_dir_right);
      mbRightKeyPressed = true;
    }
  }
  else { 
    if(mbRightKeyPressed) {
      for(KeyIter it = mKeyPressStack.begin(); it != mKeyPressStack.end(); ++it) {
        if(*it == sm_dir_right) { mKeyPressStack.erase(it); break;}
      }
      mbRightKeyPressed = false;
    }
  }

  // set our facing to the top-most key-press in our stack
  if(mKeyPressStack.size() > 0) {
    mpPlayer->SetFacing(mKeyPressStack.front());
    moveKeyPressed = true;
  }

  // set up SDL_Rects for use with CDR
  CollisionEvent event;
  SDL_Rect curPos, newPos;

  if (moveKeyPressed) {

    curPos = mpPlayer->getBox();

    // new Pos is adjusted by facing and speed
    newPos.x = curPos.x; newPos.y = curPos.y;
    if(mpPlayer->GetFacing() == sm_dir_up) { newPos.y -= mpPlayer->speed; }
    if(mpPlayer->GetFacing() == sm_dir_left) { newPos.x -= mpPlayer->speed; }
    if(mpPlayer->GetFacing() == sm_dir_down) { newPos.y += mpPlayer->speed; }
    if(mpPlayer->GetFacing() == sm_dir_right) { newPos.x += mpPlayer->speed; }

    mColMap.removeTouchableObject( mpPlayer );
    moveRectWithCdr( mColMap, curPos, newPos, event );

    // first do cdr for swag
    if(event.result == sm_cdr_partial_move || event.result == sm_cdr_cannot_move) {
      if(event.cdrObjectType == sm_cdo_swag) {
        Swag* pSwag = dynamic_cast<Swag*>(event.cdrObject);
        mpPlayer->PickupSwag( *pSwag );
        mbStatusDirty = true;
        // NOTE: remove it from mRenderables before we delete it so we don't get a segfault!
        mRenderables.erase(std::find(mRenderables.begin(), mRenderables.end(), pSwag));
        // also, remove it from the col map
        mColMap.removeTouchableObject( pSwag );
        mSwaggage.erase(std::find(mSwaggage.begin(), mSwaggage.end(), pSwag));
        delete pSwag;
        sound->Play("getswag");
      }
    }

    if(event.result == sm_cdr_success || event.result == sm_cdr_partial_move) {
      //cout << "Was able to move, adjusting player now" << endl;
      if (!sound->IsPlaying("walk")) sound->Play("walk");
      mpPlayer->move(event.finalX, event.finalY);
      mbResortRenderables = true;
    }
    else if(event.result == sm_cdr_failure) {
      cerr << "CDR failure" << endl;
      mbResortRenderables = true;
    }
    else { // could not move
      sound->Stop("walk");
//      cout << "BUMP" << endl;
    }
    mColMap.addTouchableObject( mpPlayer );

  } //  if (moveKeyPressed)
  else {
    if (sound->IsPlaying("walk")) sound->Stop("walk");
  }

  // now let babes walk around...
  for(BabeIter it = mBabes.begin(); it != mBabes.end(); ++it) {
    Babe* babe = *it;

    curPos = babe->getBox();
    newPos.x = curPos.x; newPos.y = curPos.y;
    if(babe->GetFacing() == sm_dir_up) { newPos.y -= babe->speed; }
    if(babe->GetFacing() == sm_dir_left) { newPos.x -= babe->speed; }
    if(babe->GetFacing() == sm_dir_down) { newPos.y += babe->speed; }
    if(babe->GetFacing() == sm_dir_right) { newPos.x += babe->speed; }

    mColMap.removeTouchableObject( babe );
    moveRectWithCdr( mColMap, curPos, newPos, event );

    if(event.result == sm_cdr_success || event.result == sm_cdr_partial_move) {
      babe->move(event.finalX, event.finalY);
      mbResortRenderables = true;
    }
    else {
      // pick a random number between 1 and 4
      babe->SetFacing( (sm_direction)random(1,4) );
    }

    mColMap.addTouchableObject( babe );

    // random chance for swag throw
    if( random(0,10000) <= babe->getGenerosity() ) {
      cout << "Swag throw" << endl;
      // for each surrounding empty square, there is a 50% chance 
      // that the square will be populated with some swag
      const int babex = babe->getBox().x/mColMap.cellInPixels;
      const int babey = babe->getBox().y/mColMap.cellInPixels;
      bool bPlayedSound = false;
      // NOTE: swag is 12x12, so we look at colmap squares in at least a 3-square radius
      // around the babe.  If this ever changes, we will need to adjust the loop.
      for(int y = babey-3; y <= babey+3; ++y) {
        for(int x = babex-3; x <= babex+3; ++x) {
          if(random(0,100) < 40) {
            // randomly pick swag type
            sm_swag_type swagType = (sm_swag_type)random(MIN_SWAG_TYPE, MAX_SWAG_TYPE);
            SDL_Rect rc; rc.x = x; rc.y = y; rc.w = 2; rc.h = 2;
            if(mColMap.isEmptyCol(rc)) {
//              cout << "Found space to throw swag in colmap:  " << rc.x << "," << rc.y 
//                   << "," << rc.w << "," << rc.h << endl;

              // pick a sprite ID
              vector<string>& vec = mSpriteIds[swagType];
              if(vec.size() > 0) {
                string id = vec[random(0,(int)vec.size()-1)];
//                cout << "Found a sprite id: " << id << endl;

                Swag* aSwag = new Swag(mSprites[id], x*mColMap.cellInPixels, 
                                      y*mColMap.cellInPixels, swagType, getSwagPoints(swagType));
                mSwaggage.push_back( aSwag ); // owned
                mRenderables.push_back( aSwag ); // not owned
                mColMap.addTouchableObject( aSwag ); // not owned

                if(!bPlayedSound) {
                  sound->Play("swagthrow");
                  bPlayedSound = true;
                }
              } // found a sprite id
            } // colmap was empty
          } // 50% chance
        } // x loop
      } // y loop
    } // swag throw
  } // for each babe

  // now let nerds walk around...
  for(NerdIter it = mNerds.begin(); it != mNerds.end(); ++it) {
    Nerd* nerd = *it;

    curPos = nerd->getBox();
    newPos.x = curPos.x; newPos.y = curPos.y;
    if(nerd->GetFacing() == sm_dir_up) { newPos.y -= nerd->speed; }
    if(nerd->GetFacing() == sm_dir_left) { newPos.x -= nerd->speed; }
    if(nerd->GetFacing() == sm_dir_down) { newPos.y += nerd->speed; }
    if(nerd->GetFacing() == sm_dir_right) { newPos.x += nerd->speed; }

    mColMap.removeTouchableObject( nerd );
    moveRectWithCdr( mColMap, curPos, newPos, event );

    // first do cdr for swag
    if(event.result == sm_cdr_partial_move || event.result == sm_cdr_cannot_move) {
      if(event.cdrObjectType == sm_cdo_swag) {
        Swag* pSwag = dynamic_cast<Swag*>(event.cdrObject);
        mRenderables.erase(std::find(mRenderables.begin(), mRenderables.end(), pSwag));
        mColMap.removeTouchableObject( pSwag );
        mSwaggage.erase(std::find(mSwaggage.begin(), mSwaggage.end(), pSwag));
        delete pSwag;
        sound->Play("getswag");
      } // if nerd collided with swag
    } // if nerd hit something

    if(event.result == sm_cdr_success || event.result == sm_cdr_partial_move) {
      nerd->move(event.finalX, event.finalY);
      mbResortRenderables = true;
	  // give a 4% chance that the nerds will change direction here
	  if(random(0,100) < 4) { 
		nerd->SetFacing( (sm_direction)random(1,4) );
	  }
    }
    else {
      // pick a random number between 1 and 4
      nerd->SetFacing( (sm_direction)random(1,4) );
    }

    mColMap.addTouchableObject( nerd );
  } // for each Nerd
//  dumpColMap();

  DrawScreen();
}

class TouchableObjectDepthComparison
{
    public:
        bool operator()(TouchableObject* const &first,
                        TouchableObject* const &second) const
        {
            // static long c;
            // cout << "c " << c++;
            return (first->getY() < second->getY());
        }
};

void Game::DrawScreen()
{
  // sort all renderable objects by y value so they will draw back to front
  // only sort after any onscreen object moves
  if (mbResortRenderables) {
    // cout << "sort " << mRenderables.size() << endl;
    sort(mRenderables.begin(),mRenderables.end(),TouchableObjectDepthComparison());
    // cout << endl;
    mbResortRenderables = false;
  }

  
  mpDisplay->LockScreen();

  DrawChrome();

  // draw the floor
  DrawFloor();

  // draw shadows for all TouchableObjects
  for (unsigned int idx = 0; idx < mRenderables.size(); ++idx) {
    mRenderables[idx]->Draw(mpDisplay->GetScreen(), mRenderables[idx]->getSpeed(), true);
  }

  // draw the map
  DrawBackground();

  // draw all TouchableObjects
  for (unsigned int idx = 0; idx < mRenderables.size(); ++idx) {
    mRenderables[idx]->Draw(mpDisplay->GetScreen(), mRenderables[idx]->getSpeed(), false);
  }

  // TODO: only update when i need to
  DrawStatus();

  // flip the screen now that all rendering is complete 
  mpDisplay->UnlockScreen();
  mpDisplay->Flip();
}

void Game::InitChrome()
{
  NodeArray nodes;
  XMLNode* node;
  int numNodes;
  cout << "in InitChrome" << endl;
  mpStatusAreaSurf = IMG_Load(szStatusAreaBackgroundImage);
  if (!mpStatusAreaSurf) {
    cerr << "Unable to load status area image: " << SDL_GetError() << endl;
    exit(1);
  }
  numNodes = mpConfig->getElementsByTagName("photo", &nodes);
  if(1 != numNodes) {
    if (numNodes > 1) {
      cerr << "Error!  Only one <photo> element should exist in wad.xml" << endl;
      exit(2013);
    } else {
      cerr << "Error!  Could not find the <photo> element in wad.xml" << endl;
      exit(2014);
    }
  }
  node = nodes[0];
  if (node) {
    Photo::SetBorder(node->attr["border"]);
  } else {
    cerr << "Error! Invalid <photo> element in wad.xml" << endl;
    exit(2015);
  }
}

// TODO: This should probably be removed one day...
void Game::DrawChrome()
{
  // temporary background 
  SDL_Surface* scr = mpDisplay->GetScreen();
  SDL_Rect rcMap = { 0, 0, SCREEN_SIZE_X - STATUS_SIZE_X, SCREEN_SIZE_Y };
  SDL_FillRect(scr, &rcMap, SDL_MapRGB(scr->format, 0, 0, 0)); // black
}

void Game::DrawBackground()
{
  if(mpScene->Background) {
    SDL_BlitSurface(mpScene->Background, NULL, mpDisplay->GetScreen(), &mpScene->MapRect);
  }
}

void Game::DrawFloor()
{
  if(mpScene->Floor) {
    SDL_BlitSurface(mpScene->Floor, NULL, mpDisplay->GetScreen(), &mpScene->MapRect);
  }
}

void Game::DrawStatus()
{
  // only update status once a second
  static Sint32 oldTime = 0;
  if(!mbStatusDirty && mTimeLeftInSec == oldTime) { return; }
  oldTime = mTimeLeftInSec;
  mbStatusDirty = false;

  if(!mpDisplay || !mpPlayer) { return; }

  SDL_Rect rcStatus = { STATUS_POS_X, STATUS_POS_Y, STATUS_SIZE_X, STATUS_SIZE_Y };
  SDL_BlitSurface(mpStatusAreaSurf, NULL, mpDisplay->GetScreen(), &rcStatus);

  char szTimeStr[30];
  int time_x = STATUS_POS_X + 20; int time_y = STATUS_POS_Y + 65;
  SDL_Color black = {0,0,0,255}; 
  SDL_Color time_color = {0,0,0,255}; // black by default
  if(mTimeLeftInSec > 0) {
    sprintf(szTimeStr, "Time Left: %d:%02d", mTimeLeftInSec/60, mTimeLeftInSec%60);
  }
  else {
    sprintf(szTimeStr, "Game over, chump!");
    time_color.r = 255; // red
  }

//  char szPlayerStr[30];
//  int player_x = STATUS_POS_X + 10; int player_y = STATUS_POS_Y + 120;
//  sprintf(szPlayerStr, "Pos: (%d,%d)", mpPlayer->getLeft(), mpPlayer->getTop());

  char szPlayerScore[20];
  int player_points_y = time_y + 20;
  sprintf(szPlayerScore, "Points: %d", mpPlayer->GetPoints());

  char szLocation[40];
  sprintf(szLocation, "(%s)", mpCurLevel->attr["location"].c_str());

  int cx = STATUS_POS_X + (STATUS_SIZE_X/2);

  // rendering calls go here, should cluster by font and size
  mpDisplay->SetCurrentFont("resources/fonts/VeraBd.ttf", 14);
  mpDisplay->DrawText(mpCurLevel->attr["name"].c_str(), black, 
                      cx, STATUS_POS_Y + 20, sm_disp_centerh);
  mpDisplay->SetCurrentFont("resources/fonts/Vera.ttf", 12);
  mpDisplay->DrawText(szLocation, black, cx, STATUS_POS_Y + 40, sm_disp_centerh);

  mpDisplay->SetCurrentFont("resources/fonts/Vera.ttf", 14);
  mpDisplay->DrawText(szTimeStr, time_color, cx, time_y, sm_disp_centerh);
//  mpDisplay->DrawText(szPlayerStr, time_color, player_x, player_y);

  int midx = STATUS_POS_X+(STATUS_SIZE_X/2);

  mpDisplay->DrawText(szPlayerScore, time_color, midx, player_points_y, sm_disp_centerh);

  // render swag pile
  {
    mpDisplay->DrawText("Swag Pile", black, midx, 160, sm_disp_centerh);
    int count = 0;
    int swagNum = 0;
    const int left = STATUS_POS_X + 50;
    const int top = 200;
    const int incy = 30;
    const int incx = 70;
    char szSwagCountStr[20];
    for(int swagTypeInt = MIN_SWAG_TYPE; swagTypeInt <= MAX_SWAG_TYPE; swagTypeInt++) {
      sm_swag_type swagType = (sm_swag_type)(swagTypeInt);
      count = mpPlayer->GetSwagCount(swagType);
      if(count > 0) { 
//        cout << "Found a count for swag type '" << swagType << "' = " << count << endl;
        if(mSpriteIds.find(swagType) == mSpriteIds.end()) {
          cerr << "Could not find sprite for swag pile for type " << swagType << endl;
          continue;
        }
        Sprite* pSprite = mSprites[ mSpriteIds[swagType][0] ];

        // render the sprite
        pSprite->Draw(0,0,mpDisplay->GetScreen(),
                      left+incx*(swagNum%2),top+incy*(swagNum/2));

        sprintf(szSwagCountStr, "x %d", count);
        mpDisplay->DrawText(szSwagCountStr, black, left+incx*(swagNum%2)+15, top+incy*(swagNum/2)-15);

        ++swagNum;
      }
    }
  }

  // render photo bucket
  {
    mpDisplay->DrawText("Photos", black, midx, 375, sm_disp_centerh);
    const int left = STATUS_POS_X + 20;
    const int top = 400;
    const int incy = 30;
    const int incx = 70;

    int posx = left;
    int posy = top;

    for (PhotoIter iter = mPhotos.begin(); iter != mPhotos.end(); ++iter) {
      int status = (*iter)->Draw(mpDisplay->GetScreen(), posx, posy);
      posx += PHOTO_BORDER_SIZE_X;
      // divide photos into rows
      if (posx + PHOTO_BORDER_SIZE_X + 10 > STATUS_POS_X + STATUS_SIZE_X) {
        posx = left;
        posy += PHOTO_BORDER_SIZE_Y;
      }
      cout << "Showing photo (status " << status << ") " << endl;
    }
  }
}
